package com.benyanyi.okhttp.download;

import android.content.Context;
import android.content.SharedPreferences;

import com.benyanyi.okhttp.listener.OnDownLoadObserver;
import com.benyanyi.okhttp.util.OkHttpLog;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicReference;

import io.reactivex.Observable;
import io.reactivex.ObservableEmitter;
import io.reactivex.ObservableOnSubscribe;
import io.reactivex.ObservableSource;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.functions.Function;
import io.reactivex.functions.Predicate;
import io.reactivex.schedulers.Schedulers;
import okhttp3.Call;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

/**
 * @author BenYanYi
 * @date 2018/11/29 15:16
 * @email ben@yanyi.red
 * @overview
 */
class DownloadManager {

    private static final AtomicReference<DownloadManager> INSTANCE = new AtomicReference<>();
    /**
     * 用来存放各个下载的请求
     */
    private HashMap<String, Call> downCalls;
    private HashMap<String, DownloadInfo> downInfos;
    /**
     * OKHttpClient;
     */
    private OkHttpClient mClient;
    private Context mContext;

    /**
     * 文件名后缀
     */
    private String suffix = "";
    /**
     * 是否覆盖之前的文件
     */
    private boolean isCover = false;

    /**
     * 是否为下载 false 为继续下载
     */
    private boolean isStart = true;

    private SharedPreferences sharedPreferences;
    private SharedPreferences.Editor oEditor;

    /**
     * 获得一个单例类
     *
     * @param mContext
     * @return
     */
    static DownloadManager getInstance(Context mContext) {
        for (; ; ) {
            DownloadManager current = INSTANCE.get();
            if (current != null) {
                return current;
            }
            current = new DownloadManager(mContext);
            if (INSTANCE.compareAndSet(null, current)) {
                return current;
            }
        }
    }

    private DownloadManager(Context mContext) {
        downCalls = new HashMap<>();
        downInfos = new HashMap<>();
        mClient = new OkHttpClient.Builder().build();
        sharedPreferences = mContext.getSharedPreferences("benyanyi", Context.MODE_PRIVATE);
        oEditor = sharedPreferences.edit();
        this.mContext = mContext;
    }

    /**
     * 查看是否在下载任务中
     *
     * @param url
     * @return
     */
    boolean getDownloadUrl(String url) {
        return downCalls.containsKey(url);
    }

    /**
     * 获取下载的downloadInfo
     *
     * @param url
     * @return
     */
    DownloadInfo getDownloadInfo(String url) {
        return downInfos.get(url);
    }

    /**
     * 开始下载
     *
     * @param url     下载请求的网址
     * @param isCover 是否覆盖之前的文件
     * @param suffix  文件名后缀
     */
    void download(String url, boolean isCover, String suffix) {
        this.isCover = isCover;
        this.suffix = suffix;
        this.isStart = this.sharedPreferences.getBoolean("download_start", true);
        this.download(url);
    }

    /**
     * 开始下载
     *
     * @param url                下载请求的网址
     * @param isCover            是否覆盖之前的文件
     * @param suffix             文件名后缀
     * @param onDownLoadObserver 用来回调的接口
     */
    void download(String url, boolean isCover, String suffix, OnDownLoadObserver onDownLoadObserver) {
        this.isCover = isCover;
        this.suffix = suffix;
        this.isStart = this.sharedPreferences.getBoolean("download_start", true);
        this.download(url, onDownLoadObserver);
    }

    /**
     * 开始下载
     *
     * @param url 下载请求的网址
     */
    private void download(String url) {
        DownloadObserver observer = new DownloadObserver();
        observer.setContext(mContext);
        Observable.just(url)
                //call的map已经有了,就证明正在下载,则这次不下载
                .filter(new Predicate<String>() {
                    @Override
                    public boolean test(String s) throws Exception {
                        return !downCalls.containsKey(s);
                    }
                })
                .flatMap(new Function<String, ObservableSource<?>>() {
                    @Override
                    public ObservableSource<?> apply(String s) throws Exception {
                        return Observable.just(createDownInfo(s));
                    }
                })
                //检测本地文件夹,生成新的文件名
                .map(new Function<Object, DownloadInfo>() {
                    @Override
                    public DownloadInfo apply(Object o) throws Exception {
                        return getRealFileName((DownloadInfo) o);
                    }
                })
                //下载
                .flatMap(new Function<DownloadInfo, ObservableSource<DownloadInfo>>() {
                    @Override
                    public ObservableSource<DownloadInfo> apply(DownloadInfo downloadInfo) throws Exception {
                        return Observable.create(new DownloadSubscribe(downloadInfo));
                    }
                })
                //在主线程回调
                .observeOn(AndroidSchedulers.mainThread())
                //在子线程执行
                .subscribeOn(Schedulers.io())
                //添加观察者
                .subscribe(observer);
    }

    private void download(String url, OnDownLoadObserver onDownLoadObserver) {
        Observable.just(url)
                //call的map已经有了,就证明正在下载,则这次不下载
                .filter(new Predicate<String>() {
                    @Override
                    public boolean test(String s) throws Exception {
                        return !downCalls.containsKey(s);
                    }
                })
                .flatMap(new Function<String, ObservableSource<?>>() {
                    @Override
                    public ObservableSource<?> apply(String s) throws Exception {
                        return Observable.just(createDownInfo(s));
                    }
                })
                //检测本地文件夹,生成新的文件名
                .map(new Function<Object, DownloadInfo>() {
                    @Override
                    public DownloadInfo apply(Object o) throws Exception {
                        return getRealFileName((DownloadInfo) o);
                    }
                })
                //下载
                .flatMap(new Function<DownloadInfo, ObservableSource<DownloadInfo>>() {
                    @Override
                    public ObservableSource<DownloadInfo> apply(DownloadInfo downloadInfo) throws Exception {
                        return Observable.create(new DownloadSubscribe(downloadInfo));
                    }
                })
                //在主线程回调
                .observeOn(AndroidSchedulers.mainThread())
                //在子线程执行
                .subscribeOn(Schedulers.io())
                //添加观察者
                .subscribe(onDownLoadObserver);
    }

    /**
     * 暂停
     *
     * @param url
     */
    void pause(String url) {
        remove(url);
        downInfos.remove(url);
        oEditor.putBoolean("download_start", false);
        oEditor.apply();
        oEditor.commit();
    }

    /**
     * 取消
     *
     * @param url
     */
    void cancel(String url) {
        remove(url);
        SecurityManager checker = new SecurityManager();
        DownloadInfo downloadInfo = downInfos.get(url);
        if (downloadInfo != null && downloadInfo.getFile() != null) {
            File file = downloadInfo.getFile();
            if (file.exists()) {
                checker.checkDelete(file.toString());
                if (file.isFile()) {
                    try {
                        file.delete();
                    } catch (SecurityException se) {
                        se.printStackTrace();
                    }
                }
            }
        }
        downInfos.remove(url);
        oEditor.putBoolean("download_start", true);
        oEditor.apply();
        oEditor.commit();
    }

    private void remove(String url) {
        Call call = downCalls.get(url);
        if (call != null) {
            call.cancel();//取消
        }
        downCalls.remove(url);
    }

    /**
     * 创建DownInfo
     *
     * @param url 请求网址
     * @return DownInfo
     */
    private DownloadInfo createDownInfo(String url) {
        DownloadInfo downloadInfo = new DownloadInfo(url);
        //获得文件大小
        long contentLength = getContentLength(url);
        downloadInfo.setTotal(contentLength);
        String fileName = url.substring(url.lastIndexOf("/")) + suffix;
        downloadInfo.setFileName(fileName);
        return downloadInfo;
    }

    private DownloadInfo getRealFileName(DownloadInfo downloadInfo) throws Exception {
        String fileName = downloadInfo.getFileName();
        String savePath = FileUtil.isExistDir(mContext.getPackageName());
        long downloadLength = 0, contentLength = downloadInfo.getTotal();
        File file = new File(savePath, fileName);
        if (file.exists()) {
            if (isStart && isCover) {
                file.delete();
            } else {
                //找到了文件,代表已经下载过,则获取其长度
                downloadLength = file.length();
                oEditor.putBoolean("download_start", true);
                oEditor.apply();
                oEditor.commit();
            }
        }
        if (!isCover) {
            //之前下载过,需要重新来一个文件
            int i = 1;
            while (downloadLength >= contentLength) {
                int dotIndex = fileName.lastIndexOf(".");
                String fileNameOther;
                if (dotIndex == -1) {
                    fileNameOther = fileName + "(" + i + ")";
                } else {
                    fileNameOther = fileName.substring(0, dotIndex)
                            + "(" + i + ")" + fileName.substring(dotIndex);
                }
                File newFile = new File(savePath, fileNameOther);
                file = newFile;
                downloadLength = newFile.length();
                i++;
            }
        }
        //设置改变过的文件名/大小
        downloadInfo.setProgress(downloadLength);
        downloadInfo.setFileName(file.getName());
        downloadInfo.setFile(file);
        return downloadInfo;
    }

    private class DownloadSubscribe implements ObservableOnSubscribe<DownloadInfo> {
        private DownloadInfo downloadInfo;

        DownloadSubscribe(DownloadInfo downloadInfo) {
            this.downloadInfo = downloadInfo;
        }

        @Override
        public void subscribe(ObservableEmitter<DownloadInfo> e) {
            String url = downloadInfo.getUrl();
            //已经下载好的长度
            long downloadLength = downloadInfo.getProgress();
            //文件的总长度
            long contentLength = downloadInfo.getTotal();
            //初始进度信息
            e.onNext(downloadInfo);

            Request request = new Request.Builder()
                    //确定下载的范围,添加此头,则服务器就可以跳过已经下载好的部分
                    .addHeader("RANGE", "bytes=" + downloadLength + "-" + contentLength)
                    .url(url)
                    .build();
            Call call = mClient.newCall(request);
            //把这个添加到call里,方便取消
            downCalls.put(url, call);
            downInfos.put(url, downloadInfo);
            Response response;
            try {
                response = call.execute();
                String savePath = FileUtil.isExistDir(mContext.getPackageName());
                File file = new File(savePath, downloadInfo.getFileName());
                InputStream is = null;
                FileOutputStream fileOutputStream = null;
                try {
                    if (response.body() == null) {
                        e.onError(new Throwable("下载文件为空"));
                    } else {
                        is = response.body().byteStream();
                        fileOutputStream = new FileOutputStream(file, true);
                        //缓冲数组2kB
                        byte[] buffer = new byte[2048];
                        int len;
                        while ((len = is.read(buffer)) != -1) {
                            fileOutputStream.write(buffer, 0, len);
                            downloadLength += len;
                            downloadInfo.setDownloadStatus(DownloadInfo.DOWNLOAD_ING);
                            downloadInfo.setProgress(downloadLength);
                            downInfos.put(url, downloadInfo);
                            e.onNext(downloadInfo);
                        }
                        downloadInfo.setFile(file);
                        fileOutputStream.flush();
                        downCalls.remove(url);
                        downloadInfo.setDownloadStatus(DownloadInfo.DOWNLOAD_OVER);
                        oEditor.putBoolean("download_start", true);
                        oEditor.apply();
                        oEditor.commit();
                        //关闭IO流
                        IoUtil.closeAll(is, fileOutputStream);
                        e.onComplete();//完成
                    }
                } catch (Exception ex) {
                    if (ex.getMessage() != null && ex.getMessage().equals("Software caused connection abort")) {
                        OkHttpLog.e("网络断开");
                    }
                    pause(url);
                    downloadInfo.setDownloadStatus(DownloadInfo.DOWNLOAD_ERROR);
                    e.onError(ex);
//                } finally {
                    //关闭IO流
//                    IoUtil.closeAll(is, fileOutputStream);
                }
            } catch (IOException ex) {
                pause(url);
                downloadInfo.setDownloadStatus(DownloadInfo.DOWNLOAD_ERROR);
                e.onError(ex);
            }
        }
    }

    private class DownloadingSubscribe implements ObservableOnSubscribe<DownloadInfo> {

        private String url;

        DownloadingSubscribe(String url) {
            this.url = url;
        }

        @Override
        public void subscribe(ObservableEmitter<DownloadInfo> e) throws Exception {
            DownloadInfo downloadInfo = downInfos.get(url);
            //已经下载好的长度
            long downloadLength = downloadInfo.getProgress();
            //初始进度信息
            e.onNext(downloadInfo);

            //把这个添加到call里,方便取消
            Call call = downCalls.get(url);
            if (call != null) {
                Response response = call.execute();
                String savePath = FileUtil.isExistDir(mContext.getPackageName());
                File file = new File(savePath, downloadInfo.getFileName());
                InputStream is = null;
                FileOutputStream fileOutputStream = null;
                try {
                    is = response.body().byteStream();
                    fileOutputStream = new FileOutputStream(file, true);
                    //缓冲数组2kB
                    byte[] buffer = new byte[2048];
                    int len;
                    while ((len = is.read(buffer)) != -1) {
                        fileOutputStream.write(buffer, 0, len);
                        downloadLength += len;
                        downloadInfo.setProgress(downloadLength);
                        downInfos.put(url, downloadInfo);
                        e.onNext(downloadInfo);
                    }
                    downloadInfo.setFile(file);
                    fileOutputStream.flush();
                    downCalls.remove(url);
                } finally {
                    //关闭IO流
                    IoUtil.closeAll(is, fileOutputStream);
                }
            } else {
                e.onError(new Throwable("下载失败"));
            }
            e.onComplete();//完成
        }
    }

    /**
     * 获取下载长度
     *
     * @param downloadUrl
     * @return
     */
    private long getContentLength(String downloadUrl) {
        Request request = new Request.Builder()
                .url(downloadUrl)
                .build();
        try {
            Response response = mClient.newCall(request).execute();
            if (response.isSuccessful()) {
                long contentLength = response.body().contentLength();
                response.close();
                return contentLength == 0 ? DownloadInfo.TOTAL_ERROR : contentLength;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return DownloadInfo.TOTAL_ERROR;
    }

}
